package drds.binlog.database_driver.utils;

import drds.binlog.common.Authors;
import drds.common.Author;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

@Author(name = Authors.LI_YANG)
public abstract class Bytes
{


    public static final long null_object_length = -1;//

    //string
    public static void writeNullTerminatedString(ByteArrayOutputStream byteArrayOutputStream, String string) throws IOException
    {
        byteArrayOutputStream.write(string.getBytes());
        byteArrayOutputStream.write(MSC.null_terminated_string_delimiter);
    }

    public static void writeNullTerminated(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes) throws IOException
    {
        byteArrayOutputStream.write(bytes);
        byteArrayOutputStream.write(MSC.null_terminated_string_delimiter);
    }

    public static byte[] readNullTerminatedBytes(byte[] bytes, int index)
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        for (int i = index; i < bytes.length; i++)
        {
            byte aByte = bytes[i];
            if (aByte == MSC.null_terminated_string_delimiter)
            {
                break;
            }
            byteArrayOutputStream.write(aByte);
        }
        return byteArrayOutputStream.toByteArray();
    }
    //


    //BinaryCodedLengthBytes
    public static void writeLengthAndLengthCodedBytes(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes) throws IOException
    {
        // 1. write length byte/bytes
        if (bytes.length < 252)
        {
            byteArrayOutputStream.write((byte) bytes.length);
        } else if (bytes.length < (1 << 16L))
        {
            byteArrayOutputStream.write((byte) 252);
            writeUnsigned2IntLittleEndian(byteArrayOutputStream, bytes.length);
        } else if (bytes.length < (1 << 24L))
        {
            byteArrayOutputStream.write((byte) 253);
            writeUnsigned3IntLittleEndian(byteArrayOutputStream, bytes.length);
        } else
        {
            byteArrayOutputStream.write((byte) 254);
            writeUnsigned4IntLittleEndian(byteArrayOutputStream, bytes.length);
        }
        // 2. write real data followed length of byte/bytes
        byteArrayOutputStream.write(bytes);
    }

    public static void writeUnsigned2IntLittleEndian(ByteArrayOutputStream byteArrayOutputStream, int value)//2
    {
        byteArrayOutputStream.write((byte) (value & 0xFF));
        byteArrayOutputStream.write((byte) ((value >>> 8) & 0xFF));
    }

    public static void writeUnsigned3IntLittleEndian(ByteArrayOutputStream byteArrayOutputStream, int value)//3
    {
        byteArrayOutputStream.write((byte) (value & 0xFF));
        byteArrayOutputStream.write((byte) ((value >>> 8) & 0xFF));
        byteArrayOutputStream.write((byte) ((value >>> 16) & 0xFF));
    }

    public static void writeUnsigned4IntLittleEndian(ByteArrayOutputStream byteArrayOutputStream, long value)//4
    {
        byteArrayOutputStream.write((byte) (value & 0xFF));
        byteArrayOutputStream.write((byte) (value >>> 8));
        byteArrayOutputStream.write((byte) (value >>> 16));
        byteArrayOutputStream.write((byte) (value >>> 24));
    }

    public static void writeUnsigned8IntLittleEndian(ByteArrayOutputStream byteArrayOutputStream, long value)
    {
        byteArrayOutputStream.write((byte) (value & 0xFF));
        byteArrayOutputStream.write((byte) (value >>> 8));
        byteArrayOutputStream.write((byte) (value >>> 16));
        byteArrayOutputStream.write((byte) (value >>> 24));
        byteArrayOutputStream.write((byte) (value >>> 32));
        byteArrayOutputStream.write((byte) (value >>> 40));
        byteArrayOutputStream.write((byte) (value >>> 48));
        byteArrayOutputStream.write((byte) (value >>> 56));
    }

    //
    public static long readLength(byte[] bytes, int index) throws IOException
    {
        int firstByte = bytes[index] & 0xFF;
        switch (firstByte)
        {
            case 251:
                return null_object_length;
            case 252:
                return readUnsigned2IntLittleEndian(bytes, index + 1);
            case 253:
                return readUnsigned3IntLittleEndian(bytes, index + 1);
            case 254:
                return readUnsigned8IntLittleEndian(bytes, index + 1);
            default:
                return firstByte;
        }
    }

    public static int readUnsigned2IntLittleEndian(byte[] bytes, int index)//2
    {
        int result = (bytes[index] & 0xFF) | ((bytes[index + 1] & 0xFF) << 8);
        return result;
    }

    public static int readUnsigned3IntLittleEndian(byte[] bytes, int index)//3
    {
        int result = (bytes[index] & 0xFF) | ((bytes[index + 1] & 0xFF) << 8) | ((bytes[index + 2] & 0xFF) << 16);
        return result;
    }

    public static long readUnsigned4IntLittleEndian(byte[] bytes, int index)//4
    {
        long result = (long) (bytes[index] & 0xFF) //
                | (long) ((bytes[index + 1] & 0xFF) << 8)//
                | (long) ((bytes[index + 2] & 0xFF) << 16) //
                | (long) ((bytes[index + 3] & 0xFF) << 24);//
        return result;
    }

    public static long readUnsigned8IntLittleEndian(byte[] bytes, int index)//8
    {
        long result = 0;
        int position = index;
        for (int shiftBy = 0; shiftBy < 64; shiftBy += 8)
        {
            result |= (long) ((bytes[position++] & 0xff) << shiftBy);
        }
        return result;
    }

    /**
     * index之后的字节数组 x bytes
     */
    public static byte[] readLengthBytes(byte[] bytes, int index) throws IOException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(bytes[index]);

        byte[] $bytes = null;
        int value = bytes[index] & 0xFF;
        if (value == 251)
        {
            $bytes = new byte[0];
        }
        if (value == 252)
        {
            $bytes = new byte[2];
        }
        if (value == 253)
        {
            $bytes = new byte[3];
        }
        if (value == 254)
        {
            $bytes = new byte[8];
        }
        if ($bytes != null)
        {
            System.arraycopy(bytes, index + 1, $bytes, 0, $bytes.length);
            byteArrayOutputStream.write($bytes);
        }
        //本身index的基础加上bytes.length
        return byteArrayOutputStream.toByteArray();
    }

    //writeFixedLengthBytes
    public static void writeFixedLengthBytes(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes, int length)
    {
        writeFixedLengthBytes(byteArrayOutputStream, bytes, 0, length);
    }

    public static void writeFixedLengthBytes(ByteArrayOutputStream byteArrayOutputStream, byte[] bytes, int index, int length)
    {
        for (int i = index; i < index + length; i++)
        {
            byteArrayOutputStream.write(bytes[i]);
        }
    }

    public static byte[] readFixedLengthBytes(byte[] bytes, int index, int length)
    {
        byte[] $bytes = new byte[length];
        System.arraycopy(bytes, index, $bytes, 0, length);
        return $bytes;
    }


}
